# -*- coding: utf-8 -*-
#
# prion.py
#
# Copyright (C) 2008 Marcos Pinto ('markybob') <markybob@gmail.com>
# 
# prion is free software.
# 
# This program is free software; you can redistribute it and/or modify it under 
# the terms of the GNU General Public License as published by the Free Software 
# Foundation; either version 3 of the License, or (at your option) any later 
# version. This program is distributed in the hope that it will be useful, but 
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
# details. You should have received a copy of the GNU General Public License 
# along with this program; if not, see <http://www.gnu.org/licenses>.

import os
import os.path
import sys
import pygtk
pygtk.require('2.0')
import gtk
import gtk.glade
import gobject
import pref
import signal
import time
import xdg.BaseDirectory

class prion():
    def __init__(self):
        gtk.gdk.threads_init()
        base_dir = xdg.BaseDirectory.save_config_path('prion')
        self.config = pref.Preferences(os.path.join(base_dir, 'prefs.conf'))
        self.glade = gtk.glade.XML(os.path.join(self.module_path(), "prion.glade"))
        self.glade.signal_autoconnect({"show_about": self.show_about,
                                       "preferences_cancel": self.cancel_preferences,
                                       "preferences_apply": self.save_preferences,
                                       "shutdown_tray": self.shutdown_tray })
        self.window = self.glade.get_widget("main_window")
        self.window.connect("delete_event", self.shutdown)
        self.window.set_icon(gtk.gdk.pixbuf_new_from_file(os.path.join(self.module_path(), "logo_sm.png")))
        self.tray = gtk.status_icon_new_from_file(os.path.join(self.module_path(), "logo_sm.png"))
        self.tray.connect("activate", self.tray_clicked)
        self.tray.connect("popup-menu", self.tray_popup)
        self.tray_menu = self.glade.get_widget("tray_menu")
        try:
            self.get_account()
        except:
            pass
        self.cancel_preferences()
        self.update_minute()
        self.update_stats()
        self.page_update()
        gobject.timeout_add(60000, self.update_minute)
        try:
            import gnome.ui
            self.gnome_client = gnome.ui.Client()
            self.gnome_client.connect("die", self.shutdown_tray)
        except:
            pass
        signal.signal(signal.SIGINT, self.shutdown_tray)
        signal.signal(signal.SIGTERM, self.shutdown_tray)
        if self.windows_check():
            from win32api import SetConsoleCtrlHandler
            from win32con import CTRL_CLOSE_EVENT
            from win32con import CTRL_SHUTDOWN_EVENT
            result = 0
            def win_handler(ctrl_type):
                if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
                    self.shutdown_tray()
                    return 1
            SetConsoleCtrlHandler(win_handler)
        try:
            if not self.config.get("start_in_tray"):
                self.window.show()
            gtk.gdk.threads_enter()
            gtk.main()
            gtk.gdk.threads_leave()
        except KeyboardInterrupt:
            self.shutdown_tray()
        else:
            self.shutdown_tray()

    def we_are_frozen(self):
        """Returns whether we are frozen via py2exe.
        This will affect how we find out where we are located."""
        return hasattr(sys, "frozen")


    def module_path(self):
        """ This will get us the program's directory,
        even if we are frozen using py2exe"""
        if self.we_are_frozen():
            return os.path.dirname(unicode(sys.executable, sys.getfilesystemencoding( )))

        return os.path.dirname(unicode(__file__, sys.getfilesystemencoding( )))

    def tray_clicked(self, args):
        if self.window.is_active():
            self.window.hide()
        else:
            self.window.present()

    def tray_popup(self, status_icon, button, activate_time):
        popup_function = gtk.status_icon_position_menu
        if self.windows_check():
            popup_function = None
        self.tray_menu.popup(None, None, popup_function,
            button, activate_time, status_icon)

    def show_about(self, args):
        def url_hook(dialog, url):
            self.open_url_in_browser(url)
        gtk.about_dialog_set_url_hook(url_hook)
        self.about = gtk.AboutDialog()
        self.about.set_position(gtk.WIN_POS_CENTER)
        self.about.set_name("About Prion")
        self.about.set_program_name("Prion")
        self.about.set_copyright(u'Copyright \u00A9 2008 Marcos Pinto')
        self.about.set_comments("Folding@Home Statistics")
        self.about.set_version('1.2')
        self.about.set_authors(["Marcos Pinto"])
        self.about.set_wrap_license(True)
        self.about.set_license("This program is free software; you can \
redistribute it and/or modify it under the terms of the GNU General Public \
License as published by the Free Software Foundation; either version 3 of the \
License, or (at your option) any later version. This program is distributed \
in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even \
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. \
See the GNU General Public License for more details. You should have received \
a copy of the GNU General Public License along with this program; if not, see \
<http://www.gnu.org/licenses>.")
        self.about.set_website("http://code.google.com/p/prion/")
        self.about.set_website_label("http://code.google.com/p/prion/")
        self.about.set_icon(gtk.gdk.pixbuf_new_from_file(os.path.join(self.module_path(), "logo_sm.png")))
        self.about.set_logo(gtk.gdk.pixbuf_new_from_file(os.path.join(self.module_path(), "logo.png")))
        self.about.show_all()
        self.about.run()
        self.about.hide()

    def open_url_in_browser(self, url):
        import webbrowser
        import threading
        class BrowserThread(threading.Thread):
            def __init__(self, url):
                threading.Thread.__init__(self)
                self.url = url
            def run(self):
                webbrowser.open(self.url)
        BrowserThread(url).start()
        return True

    def windows_check(self):
        import platform
        if platform.system() in ('Windows', 'Microsoft'):
            return True
        else:
            return False

    def save_preferences(self, arg=None):
        self.config.set("start_in_tray", self.glade.get_widget("chk_start_in_tray").get_active())
        self.config.set("close_to_tray", self.glade.get_widget("chk_close_to_tray").get_active())
        self.config.set("folder", self.glade.get_widget("btn_folder").get_current_folder())
        self.config.save()
        self.get_account()
        self.update_minute()
        self.page_update()
        return True

    def cancel_preferences(self, arg=None):
        self.glade.get_widget("chk_start_in_tray").set_active(self.config.get("start_in_tray"))
        self.glade.get_widget("chk_close_to_tray").set_active(self.config.get("close_to_tray"))
        try:
            self.glade.get_widget("btn_folder").set_current_folder(self.config.get("folder"))
        except:
            pass
        return True

    def shutdown(self, *args, **kwargs):
        if self.config.get("close_to_tray"):
            self.window.hide()
        else:
            self.shutdown_tray()
        return True

    def shutdown_tray(self, *args, **kwargs):
        self.tray.set_visible(False)
        try:
            gtk.main_quit()
        except RuntimeError:
            pass
        return True

    def get_account(self):
        try:
            path = os.path.join(self.config.get("folder"), "client.cfg")
            file = open(path, "rb")
            contents = file.readlines()
            file.close()
        except:
            pass
        else:
            for line in contents:
                if line.split("=")[0] == "active":
                    if line.split("=")[1].strip() == "no":
                        self.config.set("use_proxy", False)
                    else:
                        self.config.set("use_proxy", True)
                elif line.split("=")[0] == "username":
                    self.config.set("username", line.split("=")[1].strip())
                elif line.split("=")[0] == "team":
                    self.config.set("team", line.split("=")[1].strip())
                elif line.split("=")[0] == "host":
                    self.config.set("proxy_host", line.split("=")[1].strip())
                elif line.split("=")[0] == "port":
                    self.config.set("proxy_port", line.split("=")[1].strip())
                elif line.split("=")[0] == "proxy_name":
                    self.config.set("proxy_name", line.split("=")[1].strip())
                elif line.split("=")[0] == "proxy_passwd":
                    self.config.set("proxy_passwd", line.split("=")[1].strip())
                elif line.split("=")[0] == "usereg":
                    self.config.set("proxy_auth", line.split("=")[1].strip())
            self.config.save()
        return True

    def update_minute(self):
        path = os.path.join(self.config.get("folder"), "unitinfo.txt")
        try:
            file = open(path, "rb")
            contents = file.readlines()
            file.close()
        except:
            pass
        else:
            for line in contents:
                if line.split(":")[0] == "Name":
                    name = line.split(":")[1].strip()
                    self.glade.get_widget("current_name").set_text(name)
                elif line.split(":")[0] == "Tag":
                    tag = line.split(":")[1].strip()
                    self.glade.get_widget("current_tag").set_text(tag)
                elif line.split(":")[0] == "Download time":
                    due = line.split(":")[1] + ":" \
                        + line.split(":")[2] + ":"  \
                        + line.split(":")[3]
                    self.glade.get_widget("current_download").set_text(
                        due.strip())
                elif line.split(":")[0] == "Due time":
                    due = line.split(":")[1] + ":" \
                        + line.split(":")[2] + ":" \
                        + line.split(":")[3]
                    self.glade.get_widget("current_due").set_text(due.strip())
                elif line.split(":")[0] == "Progress":
                    done = int(line.split(" ")[1].split("%")[0])
                    self.glade.get_widget("current_progress").\
                        set_fraction(float(done)/100)
                    self.glade.get_widget("current_progress").\
                        set_text(line.split(" ")[1])
            self.tray.set_tooltip("---Current---\nName: " + name + "\nTag: " + tag \
                + "\nDue: " + str(due.strip()) + "\nProgress: " + str(done) + "%" \
                + "\n---User---\nScore: " + str(self.config.get("user_score")) \
                + "\nRank: " + str(self.config.get("user_rank")) + "\nWork units: " \
                + str(self.config.get("user_wu")))
            localtime = time.gmtime()
            if ((localtime[4] == 15) and ((localtime[3] == 2) or
                (localtime[3] == 4) or (localtime[3] == 6) or
                (localtime[3] == 8) or (localtime[3] == 10) or
                (localtime[3] == 12) or (localtime[3] == 14) or
                (localtime[3] == 16) or (localtime[3] == 18) or
                (localtime[3] == 20) or (localtime[3] == 22) or
                (localtime[3] == 0))):
                self.page_update()
        return True

    def strip_tags(self, value):
        import re
        return re.sub(r'<[^>]*?>', '', value)

    def page_update(self):
        path = os.path.join(self.config.get("folder"), "unitinfo.txt")
        try:
            file = open(path, "rb")
            contents = file.readlines()
            file.close()
        except:
            pass
        else:
            import urllib2
            if self.config.get("use_proxy"):
                if self.config.get("proxy_auth"):
                    proxy_info = {
                        "user" : self.config.get("proxy_name"),
                        "pass" : self.config.get("proxy_passwd"),
                        "host" : self.config.get("proxy_host"),
                        "port" : int(self.config.get("proxy_port"))
                    }
                    proxy_support = urllib2.ProxyHandler({"http" : \
                        "http://%(user)s:%(pass)s@%(host)s:%(port)d" % proxy_info})
                else:
                    proxy_info = {
                        "host" : self.config.get("proxy_host"),
                        "port" : int(self.config.get("proxy_port"))
                    }
                    proxy_support = urllib2.ProxyHandler({"http" : \
                        "http://%(host)s:%(port)d" % proxy_info})
                opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)
                urllib2.install_opener(opener)
            user_stats = urllib2.urlopen("http://fah-web.stanford.edu/cgi-bin/main.py?qtype=userpage&username=%s" % self.config.get("username"))
            team_stats = urllib2.urlopen("http://fah-web.stanford.edu/cgi-bin/main.py?qtype=teampage&teamnum=%i" % int(self.config.get("team")))
            world_stats = urllib2.urlopen("http://fah-web.stanford.edu/cgi-bin/main.py?qtype=osstats")
            for line in world_stats:
                if "Windows" in line:
                    win = line.strip().replace("<TD>", "").replace("  ", "").replace("</TD>", ":")[:-1]
                    self.config.set("win", win)
                elif "PowerPC" in line:
                    ppc = line.strip().replace("<TD>", "").replace("  ", "").replace("</TD>", ":")[:-1]
                    self.config.set("ppc", ppc)
                elif "X/Intel" in line:
                    xint = line.strip().replace("<TD>", "").replace("  ", "").replace("</TD>", ":")[:-1]
                    self.config.set("xint", xint)
                elif "Linux" in line:
                    lin = line.strip().replace("<TD>", "").replace("  ", "").replace("</TD>", ":")[:-1]
                    self.config.set("lin", lin)
                elif "<TD><A href=http://folding.stanford.edu/FAQ-ATI2>ATI GPU</A></TD>" in line:
                    ati = line.strip().replace("<TD>", "").replace("  ", "").replace("</TD>", ":")[:-1]
                    self.config.set("ati", self.strip_tags(ati))
                elif "<TD><A href=http://folding.stanford.edu/FAQ-NVIDIA>NVIDIA GPU</A></TD>" in line:
                    nv = line.strip().replace("<TD>", "").replace("  ", "").replace("</TD>", ":")[:-1]
                    self.config.set("nv", self.strip_tags(nv))
                elif "<TD><A href=http://folding.stanford.edu/FAQ-PS3>PLAYSTATION&reg;3</A></TD>" in line:
                    ps3 = line.strip().replace("<TD>", "").replace("  ", "").replace("</TD>", ":")[:-1]
                    self.config.set("ps3", self.strip_tags(ps3))
                elif "<TD>Total</TD>" in line:
                    total = line.strip().replace("<TD>", "").replace(" ", "").replace("</TD>", ":")[:-1]
                    self.config.set("total", total)
            self.config.set("team_stats", team_stats.readlines())
            if not "Stats update in progress" in self.config.get("team_stats"):
                for num, line in enumerate(self.config.get("team_stats")):
                    if "<TD><b>Date of last work unit</b></TD>" in line:
                        num_last = num + 1
                    elif "<TD><b>Active CPUs within 50 days</b></TD>" in line:
                        num_cpu = num + 1
                    elif "<TD><b>Team Id</b></TD>" in line:
                        num_id = num + 1
                    elif "<TD><b>Grand Score</b></TD>" in line:
                        num_score = num + 2
                    elif "<TD><b>Work Unit Count</b></TD>" in line:
                        num_wu = num + 2
                    elif "<TD><b>Team Ranking (incl. aggregate)</b></TD>" in line:
                        num_rank = num + 1
                try:
                    self.config.set("team_last", self.strip_tags(self.config.get("team_stats")[num_last]).strip())
                    self.config.set("team_cpu", self.strip_tags(self.config.get("team_stats")[num_cpu]).strip())
                    self.config.set("team_id", self.strip_tags(self.config.get("team_stats")[num_id]).strip())
                    self.config.set("team_score", self.strip_tags(self.config.get("team_stats")[num_score]).strip())
                    self.config.set("team_wu", self.strip_tags(self.config.get("team_stats")[num_wu]).strip())
                    self.config.set("team_rank", self.strip_tags(self.config.get("team_stats")[num_rank]).strip())
                except UnboundLocalError:
                    pass
            self.config.set("user_stats", user_stats.readlines())
            if not "Stats update in progress" in self.config.get("user_stats"):
                for num, line in enumerate(self.config.get("user_stats")):
                    if "<TD align=left><b>Date of last work unit</b></TD>" in line:
                        num_last = num + 1
                    elif "<TD align=left><b>Total score</b></TD>" in line:
                        num_score = num + 1
                    elif "<TD align=left><b>Overall rank (if points are combined)</b></TD>" in line:
                        num_rank = num + 1
                    elif "<TD align=left><b>Active processors (within 50 days)</b></TD>" in line:
                        num_cpu50 = num + 1
                    elif "<TD align=left><b>Active processors (within 7 days)</b></TD>" in line:
                        num_cpu7 = num + 1
                    elif "<TD> WU</TD>" in line:
                        num_wu = num + 2
                try:
                    self.config.set("user_last", self.strip_tags(self.config.get("user_stats")[num_last]).strip())
                    self.config.set("user_score", self.strip_tags(self.config.get("user_stats")[num_score]).strip())
                    self.config.set("user_rank", self.strip_tags(self.config.get("user_stats")[num_rank]).strip())
                    self.config.set("user_cpu50", self.strip_tags(self.config.get("user_stats")[num_cpu50]).strip())
                    self.config.set("user_cpu7", self.strip_tags(self.config.get("user_stats")[num_cpu7]).strip())
                    self.config.set("user_wu", self.strip_tags(self.config.get("user_stats")[num_wu]).strip())
                except UnboundLocalError:
                    pass
            else:
                warner = gtk.MessageDialog(parent = self,
                    flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                    buttons= gtk.BUTTONS_OK,
                    message_format="Folding@Home is current down for maintance",
                    type = gtk.MESSAGE_WARNING)
                gtk.gdk.threads_enter()
                warner.run()
                warner.destroy()
                gtk.gdk.threads_leave()
            self.config.save()
            self.update_stats()
        return True

    def update_stats(self, arg=None):
        path = os.path.join(self.config.get("folder"), "unitinfo.txt")
        try:
            file = open(path, "rb")
            contents = file.readlines()
            file.close()
        except:
            pass
        else:
            if self.config.get("win").split(":")[1]:
                self.glade.get_widget("win_tflops").set_text(self.config.get("win").split(":")[1])
            if self.config.get("lin").split(":")[1]:
                self.glade.get_widget("lin_tflops").set_text(self.config.get("lin").split(":")[1])
            if self.config.get("xint").split(":")[1]:
                self.glade.get_widget("xint_tflops").set_text(self.config.get("xint").split(":")[1])
            if self.config.get("ppc").split(":")[1]:
                self.glade.get_widget("ppc_tflops").set_text(self.config.get("ppc").split(":")[1])
            if self.config.get("ati").split(":")[1]:
                self.glade.get_widget("ati_tflops").set_text(self.config.get("ati").split(":")[1])
            if self.config.get("nv").split(":")[1]:
                self.glade.get_widget("nv_tflops").set_text(self.config.get("nv").split(":")[1])
            if self.config.get("ps3").split(":")[1]:
                self.glade.get_widget("ps3_tflops").set_text(self.config.get("ps3").split(":")[1])
            if self.config.get("total").split(":")[1]:
                self.glade.get_widget("total_tflops").set_text(self.config.get("total").split(":")[1])
            if self.config.get("win").split(":")[2]:
                self.glade.get_widget("win_active").set_text(self.config.get("win").split(":")[2])
            if self.config.get("lin").split(":")[2]:
                self.glade.get_widget("lin_active").set_text(self.config.get("lin").split(":")[2])
            if self.config.get("xint").split(":")[2]:
                self.glade.get_widget("xint_active").set_text(self.config.get("xint").split(":")[2])
            if self.config.get("ppc").split(":")[2]:
                self.glade.get_widget("ppc_active").set_text(self.config.get("ppc").split(":")[2])
            if self.config.get("ati").split(":")[2]:
                self.glade.get_widget("ati_active").set_text(self.config.get("ati").split(":")[2])
            if self.config.get("nv").split(":")[2]:
                self.glade.get_widget("nv_active").set_text(self.config.get("nv").split(":")[2])
            if self.config.get("ps3").split(":")[2]:
                self.glade.get_widget("ps3_active").set_text(self.config.get("ps3").split(":")[2])
            if self.config.get("total").split(":")[2]:
                self.glade.get_widget("total_active").set_text(self.config.get("total").split(":")[2])
            if self.config.get("win").split(":")[2]:
                self.glade.get_widget("win_total").set_text(self.config.get("win").split(":")[3])
            if self.config.get("lin").split(":")[2]:
                self.glade.get_widget("lin_total").set_text(self.config.get("lin").split(":")[3])
            if self.config.get("xint").split(":")[2]:
                self.glade.get_widget("xint_total").set_text(self.config.get("xint").split(":")[3])
            if self.config.get("ppc").split(":")[2]:
                self.glade.get_widget("ppc_total").set_text(self.config.get("ppc").split(":")[3])
            if self.config.get("ati").split(":")[2]:
                self.glade.get_widget("ati_total").set_text(self.config.get("ati").split(":")[3])
            if self.config.get("nv").split(":")[2]:
                self.glade.get_widget("nv_total").set_text(self.config.get("nv").split(":")[3])
            if self.config.get("ps3").split(":")[2]:
                self.glade.get_widget("ps3_total").set_text(self.config.get("ps3").split(":")[3])
            if self.config.get("total").split(":")[3]:
                self.glade.get_widget("total_total_cpu").set_text(self.config.get("total").split(":")[3])
            if self.config.get("team_id"):
                self.glade.get_widget("team_id").set_text(self.config.get("team_id"))
            if self.config.get("team_cpu"):
                self.glade.get_widget("team_cpu").set_text(self.config.get("team_cpu"))
            if self.config.get("team_last"):
                self.glade.get_widget("team_last").set_text(str(self.config.get("team_last")))
            if self.config.get("team_wu"):
                self.glade.get_widget("team_wu").set_text(self.config.get("team_wu"))
            if self.config.get("team_score"):
                self.glade.get_widget("team_score").set_text(self.config.get("team_score"))
            if self.config.get("team_rank"):
                self.glade.get_widget("team_rank").set_text(self.config.get("team_rank"))
            if self.config.get("username"):
                self.glade.get_widget("user_name").set_text(self.config.get("username"))
            if self.config.get("user_cpu50"):
                self.glade.get_widget("user_cpu50").set_text(self.config.get("user_cpu50"))
            if self.config.get("user_cpu7"):
                self.glade.get_widget("user_cpu7").set_text(self.config.get("user_cpu7"))
            if self.config.get("user_last"):
                self.glade.get_widget("user_last").set_text(str(self.config.get("user_last")))
            if self.config.get("user_wu"):
                self.glade.get_widget("user_wu").set_text(self.config.get("user_wu"))
            if self.config.get("user_score"):
                self.glade.get_widget("user_score").set_text(self.config.get("user_score"))
            if self.config.get("user_rank"):
                self.glade.get_widget("user_rank").set_text(self.config.get("user_rank"))


if __name__ == "__main__":
    prion = prion()
